Unix 有两个异步输入(asynchronous input)系统. 一种方法是当输入就绪时发送信号, 另一个是系统当输入被读入时发送信号. UCB 中通过设置文件描述符的 O_ASYNC 位来 实现第一种方法. 第二种方法是 POSIX 标准, 它调用 aio_read.

使用异步 I/O

新版本的反弹程序需要两种信号: SIGIO 和 SIGALRM, 所以要建立两个处理函数. SIGIO 处理函数读入击键并根据读入的数据采取行动. SIGALRM 处理函数驱动动画并 检测碰撞.

方法1: 使用 O_ASYNC

使用 O_ASYNC 需要对原有的弹球做4处改动. 首先建立和设置在键盘输入时被调用的 处理函数. 其次, 使用 fcntl 的 F_SETOWN 命令来告诉内核发送输入通知信号给进程. 其他进程可能也连接到键盘. 第三通过调用 fcntl 来设置文件描述符 0 中的 O_ASYNC 位来打开输入信号. 最后, 循环调用 pause 来等待来自计时器或键盘的信号. 当有一个从键盘来的字符到达, 内核向进程发送 SIGIO 信号. SIGIO 的处理函数使用 标准的 curses 函数 getch 来读入这个字符. 当计时器间隔超时, 内核发送以前已经 处理的 SIGALRM 信号.

/* bounce_async.c
 * purpose      animation with user control, using O_ASYNC on fd
 * note         set_ticker() sends SIGALRM, handler does animation
 *              keyboard sends SIGIO, main only calls pause()
 * compile      cc bounce_async.c set_ticker.c -l curses -o bounce_async
 */
#include <stdio.h>
#include <curses.h>
#include <signal.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

#define MESSAGE "hello"
#define BLANK   "     "

int row = 10;
int col = 0;
int dir = 1;
int delay = 200;
int done = 0;

int main()
{
    void on_alarm(int);
    void on_input(int);
    void enable_kbd_signals();

    initscr();
    crmode();
    noecho();
    clear();

    signal(SIGIO, on_input);
    enable_kbd_signals();
    signal(SIGALRM, on_alarm);
    set_ticker(delay);

    move(row, col);
    addstr(MESSAGE);

    while(!done)
        pause();
    endwin();
    return 0;
}

void on_input(int signum)
{
    int c = getch();
    if (c == 'Q' || c == EOF)
        done = 1;
    else if (c == ' ')
        dir = -dir;
}

void on_alarm(int signum)
{
    signal(SIGALRM, on_alarm);
    mvaddstr(row, col, BLANK);
    col += dir;
    mvaddstr(row, col, MESSAGE);
    refresh();

    if (dir == -1 && col <= 0)
        dir = 1;
    else if (dir == 1 && col + (int)strlen(MESSAGE) >= COLS)
        dir = -1;
}

void enable_kbd_signals()
{
    int fd_flags;
    fcntl(0, F_SETOWN, getpid());
    fd_flags = fcntl(0, F_GETFL);
    fcntl(0, F_SETFL, (fd_flags | O_ASYNC));
}

方法2: 使用 aio_read

相比设置文件描述符的 O_ASYNC 位, 使用 aio_read 更加灵活, 当然也复杂些. 对于原来的弹球程序做4处改动.

第一, 设置输入被读入时所调用的处理函数 on_input

第二, 设置 struct kbcbuf 中的变量来指名等待什么类型的输入, 当输入发生时产生 什么信号. 除了指定 SIGIO 信号, 可以执行任何信号, 甚至 SIGALRM 或 SIGINT

第三, 通过以上定义的结构体传给 aio_read 来递交输入请求. 和调用一般 read 不同 , aio_read 不会阻塞进程. 相反 aio_read 会在完成时发送信号.

最后, 实现处理函数, 函数通过调用 aio_return 来得到输入的字符.

弹球程序中需要异步读入吗

不. 用户输入阻塞程序, 间隔计时器驱动球移动的模式工作很好. 异步读入的优势在于 程序不用被输入阻塞而开一做些其他什么.

异步输入,视频游戏和操作系统

弹球游戏不需要异步输入, 但操作系统需要. 内核需要运行程序而不能把时间浪费在等 待用户输入上.

内核的异步输入是由硬件实现的, 而进程的异步输入是由软件实现的.

内核的设备驱动代码从输入端读入字符, 然后将读入的字符通过终端驱动进程处理. 如果驱动的文件描述符被设置为异步输入, 内核向进程发送信号. 当进程继续运行时, 控制转移到进程内的信号处理函数.